package comet

import (
	"context"
	"fmt"
	"net/url"
	"time"

	"github.com/Terry-Mao/goim/internal/registry"
	"google.golang.org/grpc/resolver"
)

var _ resolver.Builder = &grpcResolverBuilder{}

// 实现 resolver.Builder 接口
type grpcResolverBuilder struct {
	r       registry.Registry
	timeout time.Duration
}

func NewGrpcResolverBuilder(r registry.Registry, timeout time.Duration) (*grpcResolverBuilder, error) {
	return &grpcResolverBuilder{r: r, timeout: timeout}, nil
}

// resolver.Target 参数是调用 grpc.Dial() 时传入的 URI 被解析后的结构，用来和注册中心打交道（拉取和订阅可用服务列表）
// resolver.ClientConn 代表和服务节点的连接对象，调用其 UpdateState 方法可以更新服务节点地址，它底层通过调用 net.Dial 来和服务节点建立连接
func (b *grpcResolverBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
	r := &grpcResolver{
		r:       b.r,
		cc:      cc,
		target:  target,
		close:   make(chan struct{}),
		timeout: b.timeout,
	}
	r.resolve()
	go r.watch()
	return r, nil
}

func (b *grpcResolverBuilder) Scheme() string {
	return "discovery"
}

var _ resolver.Resolver = &grpcResolver{}

// 实现 resolver.Resolver 接口
type grpcResolver struct {
	// 客户端调用 grpc.Dial 时传入的地址字符串，经过 url.Parse 解析后的对象
	target  resolver.Target
	r       registry.Registry
	cc      resolver.ClientConn
	timeout time.Duration
	close   chan struct{}
}

func (g *grpcResolver) ResolveNow(options resolver.ResolveNowOptions) {
	// fmt.Println("options===>", options)
	// g.resolve()
}

// 服务发现的推策略，注册中心推送变更事件
func (g *grpcResolver) watch() {
	// 订阅注册中心的变更通知
	events, err := g.r.Subscribe(g.target.Endpoint())
	if err != nil {
		g.cc.ReportError(err)
		return
	}
	for {
		select {
		case ev := <-events:
			fmt.Println("ev===>", ev)
			g.resolve()
		case <-g.close:
			return
		}
	}
}

// 服务发现的拉策略，客户端从注册中心拉
func (g *grpcResolver) resolve() {
	ctx, cancel := context.WithTimeout(context.Background(), g.timeout)
	defer cancel()
	// 从注册中心获取服务可用连接地址
	instances, err := g.r.ListServices(ctx, g.target.Endpoint())
	if err != nil {
		g.cc.ReportError(err)
		return
	}
	// fmt.Println("endpoint===>", g.target.Endpoint())
	// fmt.Println("instances===>", instances)
	address := make([]resolver.Address, 0, len(instances))
	for _, si := range instances {
		// fmt.Println("si===>", si)
		var grpcAddr string
		if u, err := url.Parse(si.Addrs[0]); err == nil && u.Scheme == "grpc" {
			grpcAddr = u.Host
		}
		if grpcAddr == "" {
			continue
		}
		fmt.Println("grpcAddr===>", grpcAddr)

		address = append(address, resolver.Address{
			// 这里只能是 IP:Port 的形式
			Addr: grpcAddr,
			// WithValue 方法类似 context 中的同名方法，都是创建一个新的 map，把原来的 key-value 复制到新的，
			// 只涉及 map 的并发读，没有并发写
			// Attributes: attributes.New("weight", si.Weight).WithValue("group", si.Group),
		})
	}
	// 更新本地缓存
	err = g.cc.UpdateState(resolver.State{
		Addresses: address,
	})
	if err != nil {
		g.cc.ReportError(err)
		return
	}
}

func (g *grpcResolver) Close() {
	close(g.close)
}
